<?php
/**
 * 点大商城（www.diandashop.com） - 微信公众号小程序商城系统!
 * Copyright © 2020 山东点大网络科技有限公司 保留所有权利
 * =========================================================
 * 版本：V2
 * 授权主体：shop.guanjunbang.cn
 * 授权域名：shop.guanjunbang.cn
 * 授权码：vdcajlVLIVAgVdkfJvgY
 * ----------------------------------------------
 * 您只能在商业授权范围内使用，不可二次转售、分发、分享、传播
 * 任何企业和个人不得对代码以任何目的任何形式的再发布
 * =========================================================
 */

namespace app\common;
class WebsocketClient{
    public $ip = '127.0.0.1';
    public $port = 6225;
    public $local = PRE_URL;
    protected $socket;
	
    function __construct($ip=null,$port=null,$local=null){
		if($ip) $this->ip=$ip;
        if($port) $this->port=$port;
        if($local) $this->local=$local;
    }
    /**
     * 执行连接
     * @param string $data 要发送的数据
     */
    public function send($data){
        //打开socket
        $this->socket = fsockopen($this->ip, $this->port, $errno, $errstr, 1);
		stream_set_timeout($this->socket, 1);  // 2秒后为超时
        //握手
        if(!fwrite($this->socket, $this->getHead($data) ) ) \think\facade\Log::write('websocket create error:'.$errno.':'.$errstr);
        $headers = fread($this->socket, 2000);
        //发送数据
        if(!fwrite($this->socket, $this->hybi10Encode($data))) \think\facade\Log::write('websocket send error:'.$errno.':'.$errstr);
        $wsdata = fread($this->socket, 2000);
        //var_dump($wsdata);
        //读取响应结果，注意如果不是立即响应有可能接收不到数据
        fclose($this->socket);
        return $this->hybi10Decode($wsdata);
    }
    /**
     * 组织头信息
     * @param string $data 要发送的数据
     */
    private function getHead($data){
        $head = "GET / HTTP/1.1"."\r\n";
        $head.="Upgrade: WebSocket"."\r\n";
        $head.="Connection: Upgrade"."\r\n";
        $head.="Origin: $this->local"."\r\n";
        $head.="Host: $this->ip"."\r\n";
        $head.="Sec-WebSocket-Key: asdasdaas76da7sd6asd6as7d"."\r\n";
        $head.="Content-Length: ".strlen($data)."\r\n"."\r\n";
		//\think\facade\Log::write($head);
        return $head;
    }
    private function hybi10Decode($data){
        $bytes = $data;
        $dataLength = '';
        $mask = '';
        $coded_data = '';
        $decodedData = '';
        $secondByte = sprintf('%08b', ord($bytes[1]));
        $masked = ($secondByte[0] == '1') ? true : false;
        $dataLength = ($masked === true) ? ord($bytes[1]) & 127 : ord($bytes[1]);

        if($masked === true){
            if($dataLength === 126){
                $mask = substr($bytes, 4, 4);
                $coded_data = substr($bytes, 8);
            }elseif($dataLength === 127){
                $mask = substr($bytes, 10, 4);
                $coded_data = substr($bytes, 14);
            }else{
                $mask = substr($bytes, 2, 4);
                $coded_data = substr($bytes, 6);
            }
            for($i = 0; $i < strlen($coded_data); $i++){
                $decodedData .= $coded_data[$i] ^ $mask[$i % 4];
            }
        }else{
            if($dataLength === 126){
                $decodedData = substr($bytes, 4);
            }elseif($dataLength === 127){
                $decodedData = substr($bytes, 10);
            }else{
                $decodedData = substr($bytes, 2);
            }
        }
        return $decodedData;
    }


    private function hybi10Encode($payload, $type = 'text', $masked = true) {
        $frameHead = array();
        $frame = '';
        $payloadLength = strlen($payload);
        switch ($type) {
            case 'text':
                // first byte indicates FIN, Text-Frame (10000001):
                $frameHead[0] = 129;
                break;
            case 'close':
                // first byte indicates FIN, Close Frame(10001000):
                $frameHead[0] = 136;
                break;
            case 'ping':
                // first byte indicates FIN, Ping frame (10001001):
                $frameHead[0] = 137;
                break;
            case 'pong':
                // first byte indicates FIN, Pong frame (10001010):
                $frameHead[0] = 138;
                break;
        }
        // set mask and payload length (using 1, 3 or 9 bytes)
        if ($payloadLength > 65535) {
            $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8);
            $frameHead[1] = ($masked === true) ? 255 : 127;
            for ($i = 0; $i < 8; $i++) {
                $frameHead[$i + 2] = bindec($payloadLengthBin[$i]);
            }
            // most significant bit MUST be 0 (close connection if frame too big)
            if ($frameHead[2] > 127) {
                $this->close(1004);
                return false;
            }
        } elseif ($payloadLength > 125) {
            $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);
            $frameHead[1] = ($masked === true) ? 254 : 126;
            $frameHead[2] = bindec($payloadLengthBin[0]);
            $frameHead[3] = bindec($payloadLengthBin[1]);
        } else {
            $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
        }
        // convert frame-head to string:
        foreach (array_keys($frameHead) as $i) {
            $frameHead[$i] = chr($frameHead[$i]);
        }
        if ($masked === true) {
            // generate a random mask:
            $mask = array();
            for ($i = 0; $i < 4; $i++) {
                $mask[$i] = chr(rand(0, 255));
            }
            $frameHead = array_merge($frameHead, $mask);
        }
        $frame = implode('', $frameHead);
        // append payload to frame:
        for ($i = 0; $i < $payloadLength; $i++) {
            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
        }
        return $frame;
    }

    private function close()
    {
        @fclose($this->socket);
        $this->socket = null;
    }

}